package cn.flying.cloud.file.core.oss;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.time.ZoneId;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.amazonaws.util.IOUtils;
import io.minio.BucketExistsArgs;
import io.minio.GetObjectArgs;
import io.minio.GetObjectResponse;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.ObjectWriteResponse;
import io.minio.PutObjectArgs;
import io.minio.RemoveBucketArgs;
import io.minio.RemoveObjectArgs;
import io.minio.http.Method;

import cn.flying.cloud.base.oss.exception.OssException;
import cn.flying.cloud.base.oss.minio.MediaType;
import cn.flying.cloud.base.oss.model.BucketInfo;
import cn.flying.cloud.base.oss.model.BucketObjectInfo;
import cn.flying.cloud.base.oss.template.OssTemplate;

/**
 * minio服务类
 *
 * @author: admin
 * @date: 2023年07月12日 20:40
 * @version: 1.0
 */
public class MinioOssTemplate implements OssTemplate {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private static final ConcurrentHashMap<String, MinioClient> CLIENT_POOL = new ConcurrentHashMap<>();

    private final String endpoint;
    private final String accessKey;
    private final String secretKey;
    private final String bucketName;

    public MinioOssTemplate(String endpoint, String accessKey, String secretKey, String bucketName) {
        this.endpoint = endpoint;
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.bucketName = bucketName;
    }

    private MinioClient getClient() {
        String key = accessKey + ":" + secretKey;
        return CLIENT_POOL.computeIfAbsent(key, k -> {
                    try {
                        MinioClient minioClient = MinioClient.builder().endpoint(this.endpoint).credentials(this.accessKey, this.secretKey).build();
                        // 忽略证书校验，防止自签名证书校验失败导致无法建立连接
                        minioClient.ignoreCertCheck();
                        logger.info("MinioClient客户端连接到minio服务器：{}，accessKey：{}", this.endpoint, this.accessKey);
                        return minioClient;
                    } catch (Exception e) {
                        logger.error("MinioClient客户端初始化异常", e);
                        throw new OssException("MinioClient客户端初始化异常", e);
                    }
                }
        );
    }

    /**
     * 创建bucket
     *
     * @param bucket bucket名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean createBucket(String bucket) throws OssException {
        MinioClient minioClient = getClient();
        try {
            boolean found = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
            if (found) {
                return false;
            }
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
            return true;
        } catch (Exception e) {
            logger.error("创建bucket[{}]失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("创建bucket[" + bucket + "]失败！", e);
        }
    }

    /**
     * 获取所有的bucket
     *
     * @return
     * @throws OssException
     */
    @Override
    public List<BucketInfo> getAllBuckets() throws OssException {
        MinioClient minioClient = getClient();
        try {
            return minioClient.listBuckets().stream()
                    .map(bucket -> new BucketInfo(bucket.name(), Date.from(bucket.creationDate().toLocalDate().atStartOfDay(ZoneId.systemDefault()).toInstant())))
                    .collect(Collectors.toList());
        } catch (Exception e) {
            logger.error("获取所有的bucket失败！错误信息：[{}]", e.getMessage());
            throw new OssException("获取所有的bucket失败！", e);
        }
    }

    /**
     * 通过bucket名称删除bucket
     *
     * @param bucket bucket名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean deleteBucket(String bucket) throws OssException {
        MinioClient minioClient = getClient();
        try {
            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucket).build());
            logger.info("删除bucket[{}]成功！", bucket);
            return true;
        } catch (Exception e) {
            logger.error("删除bucket[{}]失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("删除bucket[" + bucket + "]失败！", e);
        }
    }

    /**
     * 判断bucket是否存在
     *
     * @param bucket bucket名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean bucketExists(String bucket) throws OssException {
        MinioClient minioClient = getClient();
        try {
            return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
        } catch (Exception e) {
            logger.error("获取bucket[{}]状态失败！错误信息：[{}]", bucket, e.getMessage());
            throw new OssException("获取bucket[" + bucket + "]状态失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket bucket名称
     * @param file   文件对象
     * @return
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, File file) throws OssException {
        MinioClient minioClient = getClient();
        if (!bucketExists(bucket)) {
            createBucket(bucket);
        }
        String objectName = UUID.randomUUID() + file.getName().substring(file.getName().lastIndexOf("."));
        try (InputStream stream = Files.newInputStream(file.toPath())) {
            ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName)
                    .stream(stream, stream.available(), -1)
                    .build());
            logger.info("文件对象[{}]上传到[{}]成功！", response.object(), response.bucket());
            return objectName;
        } catch (Exception e) {
            logger.error("文件对象[{}]上传到[{}]失败！错误信息：[{}]", file.getName(), bucket, e.getMessage());
            throw new OssException("文件对象[{" + file.getName() + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param stream     文件流对象
     * @return
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, String objectName, InputStream stream) throws OssException {
        MinioClient minioClient = getClient();
        if (!bucketExists(bucket)) {
            createBucket(bucket);
        }
        String name = UUID.randomUUID() + objectName.substring(objectName.lastIndexOf("."));
        try {
            ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(name)
                    .stream(stream, stream.available(), -1)
                    .build());
            logger.info("文件对象[{}]上传到[{}]成功！", response.object(), response.bucket());
            return name;
        } catch (Exception e) {
            logger.error("文件对象[{}]上传到[{}]失败！错误信息：[{}]", objectName, bucket, e.getMessage());
            throw new OssException("文件对象[{" + objectName + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 上传文件
     *
     * @param bucket      bucket名称
     * @param objectName  对象名称
     * @param stream      文件流对象
     * @param contextType 文件类型
     * @throws OssException
     */
    @Override
    public String putObject(String bucket, String objectName, InputStream stream, String contextType) throws OssException {
        MinioClient minioClient = getClient();
        if (!bucketExists(bucket)) {
            createBucket(bucket);
        }
        String name = UUID.randomUUID() + objectName.substring(objectName.lastIndexOf("."));
        try {
            ObjectWriteResponse response = minioClient.putObject(PutObjectArgs.builder().bucket(bucket).object(name)
                    .stream(stream, stream.available(), -1)
                    .contentType(contextType)
                    .build());
            logger.info("文件对象[{}]上传到[{}]成功！", response.object(), response.bucket());
            return name;
        } catch (Exception e) {
            logger.error("文件对象[{}]上传到[{}]失败！错误信息：[{}]", objectName, bucket, e.getMessage());
            throw new OssException("文件对象[{" + objectName + "}]上传到[{" + bucket + "}]失败！", e);
        }
    }

    /**
     * 通过objectName从bucket中删除对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public boolean deleteObject(String bucket, String objectName) throws OssException {
        try {
            MinioClient minioClient = getClient();
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucket).object(objectName).build());
            logger.info("从bucket[{}]删除文件对象[{}]成功！", bucket, objectName);
            return true;
        } catch (Exception e) {
            logger.error("从bucket[{}]删除文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]删除文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public BucketObjectInfo getObject(String bucket, String objectName) throws OssException {
        try {
            GetObjectResponse response = getObj(bucket, objectName);
            BucketObjectInfo objectInfo = new BucketObjectInfo();
            objectInfo.setBucket(response.bucket());
            objectInfo.setRegion(response.region());
            objectInfo.setName(response.object());
            objectInfo.setContent(IOUtils.toByteArray(response));
            return objectInfo;
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws IOException
     */
    @Override
    public byte[] getObjectBytes(String bucket, String objectName) throws OssException {
        try (GetObjectResponse response = getObj(bucket, objectName)) {
            return IOUtils.toByteArray(response);
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    private GetObjectResponse getObj(String bucket, String objectName) throws Exception {
        MinioClient minioClient = getClient();
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build());
    }

    /**
     * 获取文件对象
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param output     输出流对象
     * @return
     * @throws OssException
     */
    @Override
    public void getObject(String bucket, String objectName, OutputStream output) throws OssException {
        try (InputStream input = getObj(bucket, objectName)) {
            IOUtils.copy(input, output);
        } catch (Exception e) {
            logger.error("从bucket[{}]下载文件对象[{}]失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]下载文件对象[{" + objectName + "}]失败！", e);
        }
    }

    /**
     * 获取对象的url，默认过期时间7天
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @return
     * @throws OssException
     */
    @Override
    public String getObjectUrl(String bucket, String objectName) throws OssException {
        MinioClient minioClient = getClient();
        try {
            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .method(Method.GET)
                    .build();
            return minioClient.getPresignedObjectUrl(args);
        } catch (Exception e) {
            logger.error("从bucket[{}]获取文件对象[{}]地址失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]获取文件对象[{" + objectName + "}]地址失败！", e);
        }
    }

    /**
     * 获取对象的url
     *
     * @param bucket     bucket名称
     * @param objectName 对象名称
     * @param expiry     过期时间 (最小1 second，最大7 days)
     * @param timeUnit   时间单位
     * @return
     * @throws OssException
     */
    @Override
    public String getObjectUrl(String bucket, String objectName, Integer expiry, TimeUnit timeUnit) throws OssException {
        MinioClient minioClient = getClient();
        try {
            // 设置响应头参数
            Map<String, String> params = new HashMap<>();

            // 设置内容类型，让浏览器知道如何处理文件
            String contentType = MediaType.getContentType(objectName);
            params.put("response-content-type", contentType);

            GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                    .bucket(bucket)
                    .object(objectName)
                    .method(Method.GET)
                    .extraQueryParams(params)
                    .expiry(expiry, timeUnit)
                    .build();
            return minioClient.getPresignedObjectUrl(args);
        } catch (Exception e) {
            logger.error("从bucket[{}]获取文件对象[{}]地址失败！错误信息：[{}]", bucket, objectName, e.getMessage());
            throw new OssException("从bucket[{" + bucket + "}]获取文件对象[{" + objectName + "}]地址失败！", e);
        }
    }

    /**
     * 从bucket中获取指定前缀prefix的对象集合
     *
     * @param bucket    bucket名称
     * @param prefix    前缀
     * @param recursive 是否递归查询
     * @return
     * @throws OssException
     */
    @Override
    public List<BucketObjectInfo> getAllObjectsByPrefix(String bucket, String prefix, boolean recursive) throws OssException {
        return null;
    }

//    /**
//     * 关闭客户端
//     */
//    public void close() {
//        if (minioClient != null) {
//            minioClient = null;
//            logger.info("关闭MinioClient客户端连接！");
//        }
//    }

}
